/*
 * Chromosome.java
 *
 * Created on 28 de Agosto de 2010, 12:29
 *
 */

package intopo.evogame.partnerselection.chromosome;

import intopo.evogame.partnerselection.Cache;
import intopo.evogame.partnerselection.Database;
import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import intopo.evogame.partnerselection.config.ConstructStrategyException;
import intopo.evogame.partnerselection.config.ParsingException;
import intopo.evogame.partnerselection.config.TagException;
import intopo.game.Strategy;
import intopo.util.Util;

import cern.jet.random.Normal;

import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Scanner;

/**
 * The default chromosome used in the evolutionary algorithm to study
 * partner selection.  The basic chromosome contains genes that specify the
 * strategy to play the game and the parameters of the partner selection
 * model.  These parameters are the length of the probability and
 * combination vectors, the probability decrease factor and the utility
 * threshold.  The update policy used decreases the probability of a
 * combination that yielded an utility lower than the threshold.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Lopes da Silva Mariano</a>
 *
 * @version 1.0 28 de Agosto de 2010
 */
public class Chromosome<S extends Strategy<S>>
	implements ChromosomeFactory<S, Chromosome<S>>
{
	/**
	 * The strategy that is used to play a game.
	 */
	final public S strategy;

	/**
	 * Size of probability and combination vectors.  This field represents
	 * parameter <i>l</i> mentioned in the paper.
	 */
	final public int poolSize;
	
	/**
	 * Utility threshold used in the decision to update the probability and
	 * combination vectors.  This field represents parameter
	 * <i>u<sub>t</sub></i> mentioned in the paper.
	 * 
	 * <p><b>NOTE:</b> In this implementation, utility is equal to the
	 * payoff obtained by the player in the game.
	 */
	final public float utilityThreshold;
	
	/**
	 * Multiplication factor used when a probability is decreased because
	 * the player obtained a utility lower than {@code utilityThreshold}.
	 * This field represents parameter <i>&delta;</i> mentioned in the
	 * paper.
	 */
	final public float delta;

	/**
	 * Number of bits used in encoding probabilities of the probability vector.  Valid values range from 1 to 8.
	 */
	final public byte bitsPerProbability;
	/**
	 * Chromosome identification.
	 */
	final public int ID;
	/**
	 * Generator used in creating chromosome identifications.
	 */
	static private int idGenerator = 0;
	private Chromosome ()
	{
		this.strategy = null;
		this.poolSize = -1;
		this.utilityThreshold = -1;
		this.bitsPerProbability = -1;
		this.delta = -1;
		this.ID = -1;
	}
	/**
	 * Construct a chromosome with the given strategy and with default
	 * values for pool size, utility threshold and delta genes.
	 *
	 * <p> The default pool size is 0.  The default utility threshold is
	 * given by {@code Cache.minpayoff}. The default delta value is 0.
	 *
	 * <p> Instead of passing an instance of class {@code Game}, class
	 * {@code Cache} contains global constant parameters.  Once the
	 * application has read input parameters, it initialises the fields in
	 * {@code Cache} in order to construct all important objects.
	 *
	 * @param strategy The strategy associated with the constructed
	 * chromosome.
	 */
	public Chromosome (S strategy)
		throws IOException
	{
		this (strategy, 0, Cache.minPayoff, 0, (byte) 5);
	}
	/**
	 * Creates a new instance of Chromosome with the given genes.  This
	 * constructor is used to initialise the first generation of an
	 * evolutionary algorithm.
	 *
	 * @param strategy The strategy associated with the constructed
	 * chromosome.
	 * @param poolSize The size of the probability and combination vector.
	 * @param utilityThreshold Utility threshold used in the decision to
	 * update the probability and combination vectors.
	 * @param delta Probability update factor.
	 */
	public Chromosome (S strategy, int poolSize, float utilityThreshold, float delta, byte bitsPerProbability)
		throws IOException
	{
		this.strategy = strategy;
		this.poolSize = poolSize;
		this.utilityThreshold = utilityThreshold;
		this.delta = delta;
		this.bitsPerProbability = bitsPerProbability;
		this.ID = Chromosome.idGenerator++;

		this.writeStream ();

		assert this.poolSize >= 0
			&& this.delta >= 0.0f
			&& this.delta <= 1.0f
			&& this.bitsPerProbability > 0;
	}

	/**
	 * Creates a new instance of Chromosome using the given chromosome as
	 * the basis to initialise the fields.  This constructor is used when a
	 * chromosome is mutated due to reproduction.
	 *
	 * <p> This constructor uses global constant parameters stored in static
	 * fields of classes {@code Parameters} and {@code Cache}.  The former
	 * represents input parameters.  The latter represent constant parameters
	 * of the game used in a specific application run.
	 *
	 * @param parent  The parent chromosome that is used to construct a new instance.
	 *
	 * @throws java.io.IOException If there is an error writing the chromosome to the database
	 */
	private Chromosome (Chromosome<S> parent)
		throws IOException
	{
		assert parent != null;

		Normal normal = new Normal (0, 1, Cache.supply);
		int gene = Util.nextPositiveInt (5);
		this.strategy =           (gene !=  0 ? parent.strategy           :        parent.strategy.getMutantAllGenes ());
		this.poolSize =           (gene !=  1 ? parent.poolSize           :        Math.max (0,                Math.min (parent.poolSize            + (int) normal.nextDouble (0, Parameters.stddevNormalPoolSize),     Parameters.maxPoolSize)));
		this.utilityThreshold =   (gene !=  2 ? parent.utilityThreshold   :        Math.max (Cache.minPayoff,  Math.min (parent.utilityThreshold    + (float) normal.nextDouble (0, Cache.payoffStandardDeviation),     Cache.maxPayoff)));
		this.delta =              (gene !=  3 ? parent.delta              :        Math.max (0f,               Math.min (parent.delta               + (float) normal.nextDouble (0, Parameters.stddevNormalDelta),      1.0f)));
		this.bitsPerProbability = (gene !=  4 ? parent.bitsPerProbability : (byte) Math.max (1,                Math.min (parent.bitsPerProbability  + (int) normal.nextDouble(0, Parameters.stddevBitsPerProbability),  8)));
		this.ID = Chromosome.idGenerator++;

		this.writeStream ();

		assert this.poolSize >= 0
			&& this.delta >= 0.0f
			&& this.delta <= 1.0f;
	}
	/**
	 * Construct a chromosome using the data in the given scanner.
	 * The format of the data in the scanner depends on field {@code Parameters.defaultGenes}.
	 * If the field {@code false} the data must begin with chromosome properties.
	 * If this field is {@code true} then only a strategy must be present.
	 *
	 * <p> The following is the syntax of the data in the scanner in BNF notation:
	 * <pre>
	 * <data> ::= <properties> <strategy>
	 *          | <strategy>
	 * <properties> ::= <pool-size> <utility-threshold> <delta> <bits-per-probability>
	 * <strategy> ::= <tag> <strategy properties>
	 * </pre>
	 *
	 * <p> Token <i>tag</i> is a sequence of non-whitespace characters as defined by {@code Character.isWhitespace(char)}.
	 * This tag is used to fetch the associatated constructor in parameter {@code map}.  If there is no such key, a {@code TagException} is thrown.
	 *
	 * @see java.lang.Character#isWhitespace(char)
	 * @param scanner
	 * @param map
	 * @throws java.lang.InstantiationException
	 * @throws java.lang.IllegalAccessException
	 * @throws java.lang.IllegalArgumentException
	 * @throws java.lang.reflect.InvocationTargetException
	 * @throws java.io.IOException
	 * @throws intopo.evogame.partnerselection.TagException If the map does not contain the tag found on the scanner.
	 */
	public Chromosome (Scanner scanner, Map<String, Constructor<? extends S>> map)
		throws
			IOException,
			TagException,
			ConstructStrategyException
	{
		this.ID = Chromosome.idGenerator++;
		if (Parameters.defaultGenes) {
			this.poolSize = 0;
			this.utilityThreshold = Cache.minPayoff;
			this.delta = 0;
			this.bitsPerProbability = (byte) 5;
		}
		else {
			this.poolSize = scanner.nextInt ();
			this.utilityThreshold = scanner.nextFloat ();
			this.delta = scanner.nextFloat ();
			this.bitsPerProbability = scanner.nextByte ();
		}
		String word = scanner.next ();
		Constructor<? extends S> c;
		if (map.containsKey (word)) {
			c = map.get (word);
			try {
				this.strategy = c.newInstance (scanner);
			}
			catch (InstantiationException ex) {
				throw new ConstructStrategyException (word, ex);
			}
			catch (IllegalAccessException ex) {
				throw new ConstructStrategyException (word, ex);
			}
			catch (IllegalArgumentException ex) {
				throw new ConstructStrategyException (word, ex);
			} catch (InvocationTargetException ex) {
				throw new ConstructStrategyException (word, ex);
			}
		}
		else {
			throw new TagException (TagException.Entity.STRATEGY, word);
		}

		this.writeStream ();

		assert this.poolSize >= 0
			&& this.delta >= 0.0f
			&& this.delta <= 1.0f
			&& this.bitsPerProbability > 0;
	}
	/**
	 * Perform asexual reproduction.  The offspring is equal to this
	 * instance, or subject to mutation.  Mutation is done in the
	 * constructor {@code Chromosome(Chromosome)}.
	 *
	 * @see #Chromosome(Chromosome)
	 */
	public Chromosome reproduce ()
		throws IOException
	{
		if (Parameters.mutationProbability >= Cache.supply.nextFloat ()) {
			return new Chromosome (this);
		}
		else {
			return this;
		}
	}

	/**
	 * Prints this chromosome in the given stream.  The genes are printed in
	 * the following order: identification, pool size, utility threshold, probability update
	 * factor, bits per probability and strategy.
	 */	
	public void printStream (PrintStream ps)
	{
		ps.print (this.ID);
		ps.print ("\t");
		ps.print (this.poolSize);
		ps.print ("\t");
		ps.print (this.utilityThreshold);
		ps.print ("\t");
		ps.print (this.delta);
		ps.print ("\t");
		ps.print (this.bitsPerProbability);
		ps.print ("\t");
		this.strategy.print (ps);
//		ps.print (this.strategy.toString ());
	}
	/**
	 * Write this chromosome to the binary file {@code Database.chromosome}.
	 * <p> The record format is:
	 * <pre>
	 * | byte 1 | byte 2 | byte 3 | byte 4 | byte 5 | byte 6 | byte 7 | byte 8 |
	 * |     chromosome identification     |  pool  |     utility threshold
	 * |            PRIMARY KEY            |  size  |
	 *
	 * | byte 9 | byte 10| byte 11| byte 12| byte 13| byte 14| byte 15| byte 16|
	 *          |               delta               |bits per|    strategy     ...
	 *          |                                   |probabil|
	 * </pre>
	 * <p> The file associated with the chromosome table is {@code Database#chromosome}.
	 * <p> This method is private to enforce write responsability on the constructor.  With this choice, records are ordered by the primary key (chromosome's identification).  In order to read chromosome, a single file seek is suficient.
	 * @see intopo.evogame.partnerselection.Database#chromosome
	 * @throws java.io.IOException
	 */
	private void writeStream ()
		throws java.io.IOException
	{
		Database.singleton.chromosome.writeInt (this.ID);
		Database.singleton.chromosome.writeByte (this.poolSize);
		Database.singleton.chromosome.writeFloat (this.utilityThreshold);
		Database.singleton.chromosome.writeFloat (this.delta);
		Database.singleton.chromosome.writeByte (this.bitsPerProbability);
		this.strategy.write (Database.singleton.chromosome);
	}
	
	@Override
	public String toString ()
	{
		return String.format ("%d %f %f %d %s", this.poolSize, this.utilityThreshold, this.delta, this.bitsPerProbability, this.strategy.toString ());
	}

	@Override
	public Chromosome<S> create (Scanner scanner, Map<String, Constructor<? extends S>> map)
		throws
			TagException,
			IOException,
			ConstructStrategyException
	{
		return new Chromosome (scanner, map);
	}

	/**
	 * Return a factory of {@code Chromosome} instances
	 * @param <S1>
	 * @return A factory of chromosomes.
	 */
	static public <S1 extends Strategy<S1>> ChromosomeFactory<S1, Chromosome<S1>> getFactory ()
	{
		return new Chromosome<S1> ();
	}
	/**
	 * Parameters used by all instances of class {@code Chromosome}.
	 */
	static public class Parameters
	{
		/**
		 * Indicates if chromosomes constructed from data stored in a scanner have default values for all genes less the strategy.
		 */
		static public boolean defaultGenes = true;
		/**
		 * Probability to mutate all genes in an instance of class {@code
		 * Chromosome}.  Fields {@code maxPoolSize} are {@code
		 * stddevNormalPoolSize} are used to mutate gene {@code poolSize},
		 * while field {@code stddevNormalDelta} is used to
		 * mutate gene {@code delta}.
		 */
		static public float mutationProbability = -1;
		/**
		 * Maximum size of probability and combination vectors.  This
		 * parameter is used when mutating a chromosome.
		 *
		 * <p> Gene {@code poolSize} is mutated by adding a discrete normal
		 * distribution with mean zero and standard deviation given by {@code
		 * stddevNormalPoolSize}.  The sum is constrained in the interval
		 * ranging form 0 to this value.
		 */
		static public int maxPoolSize = -1;
		/**
		 * Standard deviation of the discrete normal distribution used to
		 * mutate gene {@code poolSize}.
		 *
		 * <p> Gene {@code poolSize} is mutated by adding a discrete normal
		 * distribution with mean zero and standard deviation given by this
		 * field.  The sum is constrained in the interval ranging form 0 to
		 * this value.
		 */
		static public float stddevNormalPoolSize = -1;
		/**
		 * Standard deviation of the normal distribution used to mutate gene
		 * {@code utilityThreshold}.
		 *
		 * <p> Gene {@code utilityThreshold} is mutated by adding a normal
		 * distribution with mean zero and standard deviation given by this
		 * field.  The sum is constrained in the interval ranging from {@code
		 * Cache.minPayoff} to {@code Cache.maxPayoff}.
		 */
		static public float stddevNormalUtilityThreshold = -1;
		/**
		 * Standard deviation of the normal distribution used to mutate gene
		 * {@code delta}.
		 */
		static public float stddevNormalDelta = -1;
		/**
		 * Standard deviation of the normal distribution used to mutate gene {@code bitsPerProbability}.
		 */
		static public float stddevBitsPerProbability = -1;
		/**
		 * Reads from the given scanner the parameters that are used by all
		 * instances of class {@code Chromosome}.  The syntax of the data in the scanner is the following:
		 * <pre>
		 * <parameter> ::= <default-genes> <mutation-probability>
		 *      <max-pool-size> <stddev-normal-pool-size> <stddev-normal-delta>
		 *      <stddev-bits-per-probability>
		 * <default-genes> ::= INTEGER <comment>
		 * <mutation-probability> ::= FLOAT <comment>
		 * <max-pool-size> ::= INTEGER <comment>
		 * <stddev-normal-pool-size> ::= FLOAT <comment>
		 * <stddev-normal-delta> ::= FLOAT <comment>
		 * <stddev-bits-per-probability> ::= FLOAT <comment>
		 * <comment> ::= CHARACTER <comment>
		 *             | END-OF-LINE
		 * </pre>
		 * 
		 * @see intopo.evogame.partnerselection.player.AbstractPlayer.Parameters#read(java.util.Scanner)
		 * @see intopo.evogame.partnerselection.EvolutionaryAlgorithm.Parameters#read(java.util.Scanner)
		 */
		static public void read (Scanner scanner)
			throws ParsingException
		{
			Parameters.defaultGenes = AbstractTextConfigFile.readInt (scanner, "default genes") == 1;
			Parameters.mutationProbability = AbstractTextConfigFile.readFloat (scanner, "chromosome mutation probability");
			Parameters.maxPoolSize = AbstractTextConfigFile.readInt (scanner, "maximum size of probability and combination vectors");
			Parameters.stddevNormalPoolSize = AbstractTextConfigFile.readFloat (scanner, "standard deviation normal distribution mutate pool size gene");
			Parameters.stddevNormalUtilityThreshold = AbstractTextConfigFile.readFloat (scanner, "standard deviation normal distribution mutate utility threshold gene");
			Parameters.stddevNormalDelta = AbstractTextConfigFile.readFloat (scanner, "standard deviation normal distribution mutate delta gene");
			Parameters.stddevBitsPerProbability = AbstractTextConfigFile.readFloat (scanner, "standard deviation normal distribution mutate bits per probability gene");
		}
		public static void write (PrintStream ps)
		{
			ps.print (Chromosome.Parameters.defaultGenes ? 1 : 0);
			ps.println ("\t# default genes");
			ps.print (Chromosome.Parameters.mutationProbability);
			ps.println ("\t# chromosome mutation probability");
			ps.print (Chromosome.Parameters.maxPoolSize);
			ps.println ("\t# l - maximum size of probability and combination vectors");
			ps.print (Chromosome.Parameters.stddevNormalPoolSize);
			ps.println ("\t# standard deviation normal distribution mutate pool size gene");
			ps.print (Chromosome.Parameters.stddevNormalUtilityThreshold);
			ps.println ("\t# standard deviation normal distribution mutate utility threshold gene");
			ps.print (Chromosome.Parameters.stddevNormalDelta);
			ps.println ("\t# standard deviation normal distribution mutate delta gene");
			ps.print (Chromosome.Parameters.stddevBitsPerProbability);
			ps.println ("\t# standard deviation normal distribution mutate bits per probability gene");
		}
		static public void print (PrintStream ps)
		{
			ps.print (mutationProbability);
			ps.print (' ');
			ps.print (maxPoolSize);
			ps.print (' ');
			ps.print (stddevNormalPoolSize);
			ps.print (' ');
			ps.print (stddevNormalUtilityThreshold);
			ps.print (' ');
			ps.print (stddevNormalDelta);
			ps.print (' ');
			ps.print (stddevBitsPerProbability);
		}
		static public void debug ()
		{
			Parameters.print (System.out);
			System.out.println ();
		}
	}
}

// Local Variables: 
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
